package satir;

import java.io.File;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import satir.card.MagicToolCard;
import satir.card.RoleCard;
import satir.card.factory.CardFactory;
import satir.cell.Board;
import satir.command.Command;
import satir.framework.JSONIOPerformer;
import satir.framework.JSONIOPerformer.JSONIOPerformerJSONDecodeException;
import satir.framework.JSONIOPerformer.JSONIOPerformerReadException;
import satir.framework.LazyInitiator;
import satir.framework.command.RegularCommandInterpreter;
import satir.framework.command.State;
import satir.framework.packaging.Packager;
import satir.framework.packaging.Packager.PackageExtractDestinactionWriteFailure;
import satir.framework.packaging.Packager.PackagerArchieveFileDestinationNotFoundException;
import satir.framework.packaging.Packager.PackagerArchieveFileToBeArchievedNotFoundException;
import satir.framework.packaging.Packager.PackagerArchieveZipFileWriteFailureException;
import satir.framework.packaging.Packager.PackagerExtractAchieveFileContentInvalidException;
import satir.framework.packaging.Packager.PackagerExtractAchieveFileNotFoundException;
import satir.player.Player;
import satir.record.GameRecord;
import satir.record.Profile;

// TODO: Auto-generated Javadoc
/**
 * The Class SatirGame.
 * @version 3.0 April 2012
 * @author Creative Family
 */
public class SatirGame {

    /** The constant. */
    private static SatirGame instant;

    /** The board. */
    private Board board;

    /** The num of player. */
    private int numOfPlayer;

    /** The current player index. */
    private int currentPlayerIndex;

    /** The players. */
    private ArrayList<Player> players;

    /** The card factory. */
    private ArrayList<CardFactory> cardFactorys;

    /** The role card. */
    private ArrayList<RoleCard> roleCards;

    /** The magic tool card. */
    private ArrayList<MagicToolCard> magicToolCards;

    /** The configure detail of command. */
    private JSONObject cmdConfig;

    /** The Constant MODE_RELEASE to be define the MODE. */
    public static final int MODE_RELEASE = 1;

    /** The Constant MODE_TEST to be define the MODE. */
    public static final int MODE_TEST = 2;

    /** The MODE to indicate the execution mode of the game. */
    private int mode = MODE_RELEASE;

    /**
     * Instantiates a new stair game. It will read the Command.config for
     * command creation
     */
    protected SatirGame() {
	players = new ArrayList<Player>();
	cardFactorys = new ArrayList<CardFactory>();
	roleCards = new ArrayList<RoleCard>();
	magicToolCards = new ArrayList<MagicToolCard>();
	board = new Board();
	JSONIOPerformer cmdConfigIO;
	try {
	    cmdConfigIO = new JSONIOPerformer("config/Command.config");
	    cmdConfig = cmdConfigIO.retrieve();
	} catch (JSONIOPerformerReadException e) {
	    e.printStackTrace();
	} catch (JSONIOPerformerJSONDecodeException e) {
	    e.printStackTrace();
	}
    }
    
    /**
     * Gets the instant.
     * 
     * @return the instant
     */
    public static SatirGame getInstant() {
	return getInstant(false);
    }
    
    /**
     * Gets the instant.
     * 
     * @param isNew to create a new instance
     * 
     * @return the instant
     */
    public static SatirGame getInstant(boolean isNew) {
	if (instant == null || isNew) {
	    instant = new SatirGame();
	}
	return instant;
    }
    
    /**
     * Sets the test mode.
     */
    public void setToTestMode() {
	mode = MODE_TEST;
    }

    /**
     * Sets the release mode.
     */
    public void setToReleaseMode() {
	mode = MODE_RELEASE;
    }

    /**
     * Checks if is test mode.
     *
     * @return the test is true or not
     */
    public boolean isTestMode() {
	return mode == MODE_TEST;
    }

    /**
     * Start the game. Control the flow of board game
     * @throws PackagerExtractAchieveFileContentInvalidException
     *             the packager extract achieve file content invalid exception
     * @throws PackageExtractDestinactionWriteFailure
     *             the package extract destination write failure
     * @throws PackagerArchieveFileDestinationNotFoundException
     *             the packager archive file destination not found exception
     * @throws PackagerArchieveFileToBeArchievedNotFoundException
     *             the packager archive file to be archived not found
     *             exception
     * @throws PackagerArchieveZipFileWriteFailureException
     *             the packager archive zip file write failure exception
     */
    public void startGame()
	    throws PackagerExtractAchieveFileContentInvalidException,
	    PackageExtractDestinactionWriteFailure,
	    PackagerArchieveFileDestinationNotFoundException,
	    PackagerArchieveFileToBeArchievedNotFoundException,
	    PackagerArchieveZipFileWriteFailureException {
	// initialize
	initFactory();
	// Begin to receive all initial input from players
	initInput();
	// Game begin
	currentPlayerIndex = 0;

	// main flow
	while (true) {
	    State satirGameState;
	    do {
		System.out
			.println("-------------------------------------------------");
		System.out.println("Now it is your turn, "
			+ players.get(currentPlayerIndex).getName());
		satirGameState = new State();
		RegularCommandInterpreter.execute("SatirGameCommand",
			satirGameState);
	    } while (satirGameState.getState() == State.STATE_PROCESSING);

	    if (players.get(currentPlayerIndex).isWin()) {
		System.out.println(players.get(currentPlayerIndex).getName()
			+ " is winner!");
		satirGameState.setState(State.STATE_EXIT);
	    }
	    if (satirGameState.getState() == State.STATE_EXIT) {
		endGame();
		break;
	    }
	    currentPlayerIndex = (currentPlayerIndex + 1) % numOfPlayer;
	}
    }

    /**
     * Inits the factory.
     */
    private void initFactory() {
	try {
	    String[] cardFactoryString = {"RoleCardFactory"};
	    // create card factory
	    for (int i = 0; i < cardFactoryString.length; i++) {
		cardFactorys.add((CardFactory) Class.forName(
			"satir.card.factory." + cardFactoryString[i])
			.newInstance());
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }

    /**
     * Initialization of game by user input.
     */
    private void initInput() {
	try {
	    // Get the execution order of command from cmdConfig
	    JSONArray initCmdOrder = cmdConfig.getJSONArray("initCmdOrder");
	    for (int i = 0; i < initCmdOrder.length(); i++) {
		// Use LazyInitiator to create an instance of command for
		// execution
		Command cmd = (Command) LazyInitiator.get(
			initCmdOrder.getString(i), initCmdOrder.getString(i),
			true);
		cmd.execute();
		// create role card only after inputting number of player
		// TODO should be removed
		if (i == 0) {
		    for (int j = 0; j < numOfPlayer; j++) {
			roleCards.add((RoleCard) cardFactorys.get(0)
				.createCard());
		    }
		}
	    }
	} catch (JSONException e) {
	    e.printStackTrace();
	}
	// place the player to board
	for (int i = 0; i < numOfPlayer; i++) {
	    players.get(i).setCurrentCell(board.getHead());
	    board.getHead().addPlayer(players.get(i));
	}
    }

    /**
     * Show players list.
     */
    public void showPlayersList() {
	System.out.println("Here is the players list:");
	for (int i = 0; i < numOfPlayer; i++) {
	    String playerOrder = getPlayerOrderString(i + 1);
	    System.out.println(playerOrder + " player ["
		    + players.get(i).getName() + "] figure ["
		    + players.get(i).getFigure() + "] role :"
		    + players.get(i).getRole().getName());
	}
	System.out.println("\n");
    }

    /**
     * Gets the player order string.
     * @param place
     *		the place
     * @return the player order string
     */
    private String getPlayerOrderString(final int place) {
	String playerOrder;
	switch (place) {
	case 1:
	    playerOrder = "1st";
	    break;
	case 2:
	    playerOrder = "2nd";
	    break;
	case 3:
	    playerOrder = "3rd";
	    break;
	default:
	    playerOrder = (place) + "th";
	    break;
	}
	return playerOrder;
    }

    /**
     * End game.
     * @throws PackagerExtractAchieveFileContentInvalidException
     *             the packager extract achieve file content invalid exception
     * @throws PackageExtractDestinactionWriteFailure
     *             the package extract destination write failure
     * @throws PackagerArchieveFileDestinationNotFoundException
     *             the packager archive file destination not found exception
     * @throws PackagerArchieveFileToBeArchievedNotFoundException
     *             the packager archive file to be archived not found
     *             exception
     * @throws PackagerArchieveZipFileWriteFailureException
     *             the packager archive zip file write failure exception
     */
    public void endGame()
	    throws PackagerExtractAchieveFileContentInvalidException,
	    PackageExtractDestinactionWriteFailure,
	    PackagerArchieveFileDestinationNotFoundException,
	    PackagerArchieveFileToBeArchievedNotFoundException,
	    PackagerArchieveZipFileWriteFailureException {
	File runtimeDir = new File("temp/");
	if (!runtimeDir.exists()) {
	    runtimeDir.mkdir();
	}
	for (int i = 0; i < players.size(); i++) {
	    Packager packager = new Packager(players.get(i).getName() + ".save");
	    String directory = "temp/" + players.get(i).getName() + "/";
	    File dir = new File(directory);
	    if (!dir.exists()) {
		dir.mkdir();
	    }
	    if (dir.isDirectory()) {
		deleteSubFile(new File(directory + '.'));
	    }
	    try {
		packager.extract("save/", "temp/" + players.get(i).getName()
			+ "/");
	    } catch (PackagerExtractAchieveFileNotFoundException e1) {
		dir.mkdir();
	    }
	    GameRecord record = (GameRecord) LazyInitiator.get("GameRecord",
		    "GameRecord");
	    record.saveToJSON();
	    File file = new File("temp/" + record.getDBFilename());

	    file.renameTo(new File(dir, file.getName()));
	    Profile profile = (Profile) LazyInitiator.get("Profile", players
		    .get(i).getName());
	    profile.addGameRecords(record);
	    profile.saveToJSON();

	    packager.achieve("temp/" + players.get(i).getName() + "/", "save/");
	}
	System.out.println("");
    }

    /**
     * Delete sub file.
     * @param file
     *		the file object
     */
    private void deleteSubFile(final File file) {
	String[] files = file.list();
	for (int i = 0; i < files.length; i++) {
	    File subfile = new File(file, files[i]);
	    if (subfile.isDirectory()) {
		deleteSubFile(subfile);
	    }
	}
    }

    /**
     * Sets the number of player.
     * @param numOfPlayer
     *		the new number of player
     */
    public void setNumOfPlayer(final int numOfPlayer) {
	this.numOfPlayer = numOfPlayer;
    }

    /**
     * Gets the number of player.
     * @return the number of player
     */
    public int getNumOfPlayer() {
	return numOfPlayer;
    }

    /**
     * Gets the current player.
     * @return the current player
     */
    public Player getCurrentPlayer() {
	return players.get(currentPlayerIndex);
    }

    /**
     * Gets the board.
     * @return the board
     */
    public Board getBoard() {
	return board;
    }

    /**
     * Gets the players.
     * @param i
     *		the index of player
     * @return the players
     */
    public Player getPlayers(final int i) {
	return players.get(i);
    }

    /**
     * Gets the array list of player.
     * @return the array list of player
     */
    public ArrayList<Player> getArrayListPlayer() {
	return players;
    }

    /**
     * Sets the array list of player.
     * @param players
     *		the new array list of player
     */
    public void setArrayListPlayer(final ArrayList<Player> players) {
	this.players = players;
    }

    /**
     * Gets the array list of role card.
     * @return the array list of role card
     */
    public ArrayList<RoleCard> getArrayListRoleCard() {
	return roleCards;
    }

    /**
     * Gets the array list of magic tool card.
     * @return the array list of magic tool card
     */
    public ArrayList<MagicToolCard> getArrayListMagicToolCard() {
	return magicToolCards;
    }

    /**
     * The main method.
     * @param args
     *		the arguments
     * @throws PackagerExtractAchieveFileContentInvalidException
     *		the packager extract achieve file content invalid exception
     * @throws PackageExtractDestinactionWriteFailure
     *		the package extract destination write failure
     * @throws PackagerArchieveFileDestinationNotFoundException
     *		the packager archive file destination not found exception
     * @throws PackagerArchieveFileToBeArchievedNotFoundException
     *		the packager archive file to be archived not found
     *             exception
     * @throws PackagerArchieveZipFileWriteFailureException
     *		the packager archive zip file write failure exception
     */
    public static void main(final String[] args)
	    throws PackagerExtractAchieveFileContentInvalidException,
	    PackageExtractDestinactionWriteFailure,
	    PackagerArchieveFileDestinationNotFoundException,
	    PackagerArchieveFileToBeArchievedNotFoundException,
	    PackagerArchieveZipFileWriteFailureException {

	SatirGame stairGame = new SatirGame();
	// Set test mode, if indicated
	if (isTestModeArgument(args)) {
	    stairGame.setToTestMode();
	}
	stairGame.startGame();
    }

    /**
     * Checks if is test mode argument.
     * @param args
     * 		the args
     * @return true if this string array is the same as test; false otherwise.
     */
    private static boolean isTestModeArgument(final String[] args) {
	boolean testMode = false;
	if (args.length > 0) {
	    testMode = args[0].equals("test");
	}
	return testMode;
    }

}
